package util

import (
	"errors"
	"fmt"
	"gorm.io/gorm"
	"reflect"
	"regexp"
	"strings"
	"time"
)

func DBQuerySql(sqlSer *gorm.DB, sql string, values ...interface{}) (list []map[string]string, err error) {
	defer func() {
		if r := recover(); r != nil {
			fmt.Println(r)
			err = errors.New("null")
		}
	}()
	rows, err := sqlSer.Raw(sql, values...).Rows() // (*sql.Rows, error)
	//defer rows.Close()
	if err != nil {
		return
	}
	columns, _ := rows.Columns()
	columnLength := len(columns)
	cache := make([]interface{}, columnLength) //临时存储每行数据
	for index := range cache {                 //为每一列初始化一个指针
		var a interface{}
		cache[index] = &a
	}
	var o string
	for rows.Next() {
		err = rows.Scan(cache...)
		if err != nil {
			return
		}
		item := make(map[string]string)
		for i, data := range cache {
			bytes := *data.(*interface{})
			switch bytes.(type) {
			case int64:
				o = fmt.Sprintf("%d", bytes.(int64))
			case float64:
				o = fmt.Sprintf("%f", bytes.(float64))
			case []uint8:
				o = UiToS(bytes.([]uint8))
			case time.Time:
				o = TimeUtil.DateToyMdHmsSepTo(bytes.(time.Time))
			default:
				o = ""
			}
			item[columns[i]] = o //取实际类型
		}
		list = append(list, item)
	}
	return
}
func UiToS(bs []uint8) string {
	var ba = make([]byte, 0)
	for _, b := range bs {
		ba = append(ba, b)
	}
	return string(ba)
}

func SqlToObj() {

}

type SqlObj struct {
	Obj          string            `json:"obj"` // sql语句
	Json         string            `json:"json"`
	ClassName    string            `json:"class_name"`    // 类名
	Field        string            `json:"field"`         // 字段总集
	FieldData    string            `json:"field_data"`    // 字段总集
	PrimaryKey   string            `json:"primary_key"`   // 主键
	Key          []string          `json:"key"`           // 索引
	UniqueKey    []string          `json:"unique_key"`    // 唯一索引
	FieldMap     map[string]string `json:"field_map"`     // 字段对应
	FieldType    map[string]string `json:"field_type"`    // 字段类型对应
	TableComment string            `json:"table_comment"` // 对象备注
	TableName    string            `json:"table_name"`    // 对象备注
	ToString     string            `json:"to_string"`
}

func (s *SqlObj) In(str string) (err error) {
	defer func() {
		if r := recover(); r != nil {
			fmt.Println(reflect.TypeOf(r).Name())
			fmt.Println(r)
			err = errors.New("解析失败")
			return
		}
	}()
	str = strings.ReplaceAll(str, "“", "`")
	str = strings.ReplaceAll(str, "( ", "(")
	str = strings.ReplaceAll(str, " (", "(")
	str = strings.ReplaceAll(str, "\n", " ")
	str = strings.ReplaceAll(str, " ,", ",")
	str = strings.ReplaceAll(str, ", ", ",")
	str = strings.ReplaceAll(str, "',", "'\n")
	str = strings.ReplaceAll(str, "),", ")\n")
	str = strings.ReplaceAll(str, "NULL,", "NULL\n")
	str = strings.ReplaceAll(str, "BTREE,", "BTREE\n")
	strss, err := RegexpFind(fmt.Sprintf(`CREATE TABLE [%s](.*?)[%s]((\s|.)*?)ENGINE.*`, "\"|`", "\"|`"), str)
	s.TableName = strss[1]
	s.ClassName = AaBb(s.TableName)
	s.getTableComment()
	s.fieldData(strss[2])
	return
}

func AaBb(str string) string {
	strs := strings.Split(str, "_")
	str = ""
	for _, s := range strs {
		str += strings.ToUpper(s[:1]) + s[1:]
	}
	return str
}
func AaaBb(str string) string {
	strs := strings.Split(str, "_")
	str = ""
	for i, s := range strs {
		if i == 0 {
			str += s
		} else {
			str += strings.ToUpper(s[:1]) + s[1:]
		}
	}
	return str
}
func (s *SqlObj) fieldData(str string) {
	strs := strings.Split(str, "\n")
	var (
		fieldArr []string
		//FieldMap  = make(map[string]string)
		FieldAa   = make(map[string]string)
		FieldType = make(map[string]string)
		class     = fmt.Sprintf("type %s struct {\n", s.ClassName)
	)
	for _, v := range strs {
		if i, t, gt, rm := IsField(v); i {
			vs := getField(v)
			fieldArr = append(fieldArr, vs)
			FieldType[vs] = t
			VS := AaBb(vs)
			FieldAa[vs] = VS
			if s.Json == "aBb" {
				vs = AaaBb(vs)
			}
			class += fmt.Sprintf("\t%s %s `json:\"%s\" %s`\n", VS, gt, vs, rm)
		}
	}
	class += fmt.Sprintf("} //%s\n", s.TableComment)
	s.ToString += class
	s.ToString += fmt.Sprintf("var %sField = \"`%s`\"\n", s.ClassName, strings.Join(fieldArr, "`,`"))
}
func getField(str string) string {
	str = strings.TrimSpace(str)
	if str[:1] == "(" {
		str = strings.TrimSpace(str[1:])
	}
	if strings.Index(str, "`") != 0 {
		strs := strings.Split(str, " ")
		return strs[0]
	}
	v := fmt.Sprintf(".*[%s](.*?)[%s](.*?)", "\"|`", "\"|`")
	strss, err := RegexpFind(v, str)
	if err != nil {
		panic(err.Error())
	}
	return strss[1]

}
func (s *SqlObj) getTableComment() {
	defer func() {
		if r := recover(); r != nil {
			fmt.Println(reflect.TypeOf(r).Name())
			fmt.Println(r)
			return
		}
	}()
	v := fmt.Sprintf(".* COMMENT\\s*=\\s'(.*?)]'")
	sirs, err := RegexpFind(v, s.Obj)
	if err != nil {
		panic(err.Error())
	}
	s.TableComment = s.TableName + " " + sirs[1]
}

var fieldType = map[string]string{

	"tinyint":   "int",
	"smallint":  "int",
	"mediumint": "int",
	"int":       "int",
	"bigint":    "int64",
	"decimal":   "float64",
	"float":     "float64",
	"double":    "float64",

	"real":    "string",
	"bit":     "string",
	"boolean": "string",
	"serial":  "string",

	"date":      "*time.Time",
	"datetime":  "*time.Time",
	"timestamp": "*time.Time",
	"time":      "*time.Time",
	"year":      "*time.Time",

	"char":       "string",
	"varchar":    "string",
	"tinytext":   "string",
	"text":       "string",
	"mediumtext": "string",
	"longtext":   "string",

	"binary":     "[]byte",
	"varbinary":  "[]byte",
	"tinyblob":   "[]byte",
	"mediumblob": "[]byte",
	"blob":       "[]byte",
	"longblob":   "[]byte",

	"enum": "string",
	"set":  "string",

	"geometry":           "string",
	"point":              "string",
	"linestring":         "string",
	"polygon":            "string",
	"multipoint":         "string",
	"multilinestring":    "string",
	"multipolygon":       "string",
	"geometrycollection": "string",
	"json":               "string"}
var fieldSqlType = []string{
	"tinyint", "smallint", "mediumint", "int", "bigint", "decimal", "float", "double", "real", "bit", "boolean", "serial", "date", "datetime", "timestamp", "time", "year", "char", "varchar", "tinytext", "text", "mediumtext", "longtext", "binary", "varbinary", "tinyblob", "mediumblob", "blob", "longblob", "enum", "set", "geometry", "point", "linestring", "polygon", "multipoint", "multilinestring", "multipolygon", "geometrycollection", "json"}

func IsField(str string) (is bool, field string, goType string, rm string) {
	for field, goType = range fieldType {
		is = strings.Contains(str, fmt.Sprintf(" %s ", field)) ||
			strings.Contains(str, fmt.Sprintf(" %s(", field))
		if is {
			rm, _ = getAll(field, str)
			return
		}

	}
	return
}

/*
	数字
TINYINT		1 字节整数，有符号范围从 -128 到 127，无符号范围从 0 到 255
SMALLINT		2 字节整数，有符号范围从 -32768 到 32767，无符号范围从 0 到 65535
MEDIUMINT		3 字节整数，有符号范围从 -8388608 到 8388607，无符号范围从 0 到 16777215
INT		4 字节整数，有符号范围从 -2147483648 到 2147483647，无符号范围从 0 到 4294967295
BIGINT		8 字节整数，有符号范围从 -9223372036854775808 到 9223372036854775807，无符号范围从 0 到 18446744073709551615

DECIMAL		定点数（M，D）- 整数部分（M）最大为 65（默认 10），小数部分（D）最大为 30（默认 0）
FLOAT		单精度浮点数，取值范围从 -3.402823466E+38 到 -1.175494351E-38、0 以及从 1.175494351E-38 到 3.402823466E+38
DOUBLE		双精度浮点数，取值范围从 -1.7976931348623157E+308 到 -2.2250738585072014E-308、0 以及从 2.2250738585072014E-308 到 1.7976931348623157E+308
REAL		DOUBLE 的别名（例外：REAL_AS_FLOAT SQL 模式时它是 FLOAT 的别名）

BIT		位类型（M），每个值存储 M 位（默认为 1，最大为 64）
BOOLEAN		TINYINT(1) 的别名，零值表示假，非零值表示真
SERIAL		BIGINT UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE 的别名

	日期与时间
DATE		日期，支持的范围从 1000-01-01 到 9999-12-31
DATETIME		日期与时间，支持的范围从 1000-01-01 00:00:00 到 9999-12-31 23:59:59
TIMESTAMP		时间戳，范围从 1970-01-01 00:00:01 UTC 到 2038-01-09 03:14:07 UTC，存储为自纪元（1970-01-01 00:00:00 UTC）起的秒数
TIME		时间，范围从 -838:59:59 到 838:59:59
YEAR		四位数（4，默认）或两位数（2）的年份，取值范围从 70（1970）到 69（2069）或从 1901 到 2155 以及 0000

	文本
CHAR		定长（0-255，默认 1）字符串，存储时会向右边补足空格到指定长度
VARCHAR		变长（0-65,535）字符串，最大有效长度取决于最大行大小

TINYTEXT		最多存储 255（2^8 - 1）字节的文本字段，存储时在内容前使用 1 字节表示内容的字节数
TEXT		最多存储 65535（2^16 - 1）字节的文本字段，存储时在内容前使用 2 字节表示内容的字节数
MEDIUMTEXT		最多存储 16777215（2^24 - 1）字节的文本字段，存储时在内容前使用 3 字节表示内容的字节数
LONGTEXT		最多存储 4294967295 字节即 4GB（2^32 - 1）的文本字段，存储时在内容前使用 4 字节表示内容的字节数

BINARY		类似于 CHAR 类型，但其存储的是二进制字节串而不是非二进制字符串
VARBINARY		类似于 VARCHAR 类型，但其存储的是二进制字节串而不是非二进制字符串

TINYBLOB		最多存储 255（2^8 - 1）字节的 BLOB 字段，存储时在内容前使用 1 字节表示内容的字节数
MEDIUMBLOB		最多存储 16777215（2^24 - 1）字节的 BLOB 字段，存储时在内容前使用 3 字节表示内容的字节数
BLOB		最多存储 65535（2^16 - 1）字节的 BLOB 字段，存储时在内容前使用 2 字节表示内容的字节数
LONGBLOB		最多存储 4294967295 字节即 4GB（2^32 - 1）的 BLOB 字段，存储时在内容前使用 4 字节表示内容的字节数

ENUM		枚举，可从最多 65535 个值的列表中选择或特殊的错误值 ''
SET		可从最多 64 个成员中选择集合为一个值

	空间
GEOMETRY		一种能存储任意类型几何体的类型
POINT		二维空间中的点
LINESTRING		点之间的线性插值曲线
POLYGON		多边形
MULTIPOINT		点的集合
MULTILINESTRING		点之间的线性插值曲线的集合
MULTIPOLYGON		多边形的集合
GEOMETRYCOLLECTION		任意类型几何体对象的集合

	JSON
JSON		存储并可高效访问 JSON （JavaScript 对象） 文档中的数据

*/

/*
	数字
TINYINT
SMALLINT
MEDIUMINT
INT
BIGINT

DECIMAL
FLOAT
DOUBLE
REAL

BIT
BOOLEAN
SERIAL

	日期与时间
DATE
DATETIME
TIMESTAMP
TIME
YEAR

	文本
CHAR
VARCHAR

TINYTEXT
TEXT
MEDIUMTEXT
LONGTEXT

BINARY
VARBINARY

TINYBLOB
MEDIUMBLOB
BLOB
LONGBLOB

ENUM
SET

	空间
GEOMETRY
POINT
LINESTRING
POLYGON
MULTIPOINT
MULTILINESTRING
MULTIPOLYGON
GEOMETRYCOLLECTION

	JSON
JSON

*/

func RegexpFind(v, s string) (strss []string, err error) {
	defer func() {
		if r := recover(); r != nil {
			//fmt.Println(r)
			err = errors.New("错误")
		}
	}()
	return regexp.MustCompile(v).FindAllStringSubmatch(s, -1)[0], nil
}

func getDefault(str string) (err error, val, cs string) {
	strs, err := RegexpFind(".*(DEFAULT\\s*(.*?)) .*", str)
	if err != nil {
		return
	}
	val = strs[2]
	if strings.ToUpper(val) == "NULL" {
		return
	}
	return err, val, fmt.Sprintf("default:%s;", val)
} // 获取默认值
func getComment(str string) (err error, val, cs string) {
	strs, err := RegexpFind(".*(COMMENT\\s*'(.*?)').*", str)
	if err != nil {
		return
	}
	val = strs[2]
	return err, val, fmt.Sprintf("comment:%s;", val)
} // 获取备注
func getType(t, str string) (err error, val, cs string) {
	strs, err := RegexpFind(fmt.Sprintf(".*(%s\\((.*?)\\)).*", t), str)
	if err != nil {
		return
	}
	val = strs[1]
	return err, val, fmt.Sprintf("type:%s;", val)
} // 获取类型
func getAll(t, str string) (cs string, err error) {
	sirs, err := RegexpFind(fmt.Sprintf(".*(%s\\((.*?)\\)).*(DEFAULT\\s*(.*?)) .*(COMMENT\\s*'(.*?)').*", t), str)
	if err != nil {
		cs = `gorm:"`
		err, _, s := getType(t, str)
		if err != nil {
			return "", err
		}
		cs += s
		err, _, s = getDefault(str)
		if err == nil {
			cs += s
		}
		err, _, s = getComment(str)
		if err == nil {
			cs += s
		}
		cs += `"`
	} else {
		var Type = sirs[1]
		cs = fmt.Sprintf("gorm:\"type:%s;", Type)
		var d = sirs[4]
		if !strings.Contains(d, "NULL") {
			cs += fmt.Sprintf("default:%s;", d)
		}
		cs += fmt.Sprintf("comment:%s;\"", sirs[6])
	}

	if cs != "" {
		return cs, nil
	}

	return
}
